package apibasej.basic.misc;

import java.io.IOException;
import java.util.Arrays;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import apibasej.basic.exception.APIBaseRuntimeException;


public class UtilCodec {

	
	// ver TesteCode para testes !!!!!
	
	public static String encodeHexLowerCase(byte[] bytes){
		return encodeHex(bytes).toLowerCase();
	}
	public static String encodeHexUpperCase(byte[] bytes){
		return encodeHex(bytes).toUpperCase();
	}
	public static String encodeHex(byte[] bytes){
		
		String hex = encodeHexCommonsCodec(bytes); // melhor desempenho e mais confiável;
		
		//String hex = new BigInteger(1,bytes).toString(16);// tem que ser BigInteger com o 1, indicando que é positivo, para o BigInteger tratar cada byte como ele realmente é
		//hex = completeZerosLeft( hex , bytes.length*2 );
		
		/***** TEST! REMOVE THIS! para testar UtilMathBase ... retirar esta verificação depois
		String hex2 = completeZerosLeft( new UtilMathBase(Base.HEX).decimalToOther(bytes),bytes.length*2 );
		if( !hex.equalsIgnoreCase(hex2) ){
			throw new APIBaseRuntimeException("Encode hex of UtilMathBase do not match! "+hex+"!="+hex2);
		}
		String hex3 = encodeHexCommonsCodec(bytes);
		if( !hex.equalsIgnoreCase(hex3) ){
			throw new APIBaseRuntimeException("Encode hex of Commons Codec do not match! "+hex+"!="+hex3);
		}
		*/
		/*
		// REMOVER !
		byte[] testeDec = decodeHex(hex);
		if( !Arrays.equals(bytes, testeDec) ){
			throw new APIBaseRuntimeException("Decode Hex do not match!");
		}
		*/
		//**************** 
		
		return hex;
	}
	
	public static byte[] decodeHex(String hex){
		int lenHex = hex.length();
		//if(hex.startsWith("-")) lenHex -= 1; // não deve receber negativo ??? !!!!
		if (lenHex%2!=0) throw new APIBaseRuntimeException("Odd number of characters!");
		
		byte[] resp = decodeHexCommonsCodec(hex);// melhor desempenho e mais confiável
		
		//byte[] resp = new BigInteger(hex,16).toByteArray();
		// ajusta os zeros a esquerda porque o BigInteger coloca um zero a esquerda se o primeiro byte for negativo (prim bit 1) e no criação do BigInteger foi indicado que ele é positivo com a flag 1 ou com a string que não começa com -
		//resp = ajustZerosLeft(resp, lenHex/2);
		
		//***** TEST! REMOVE THIS! para testar UtilMathBase ... retirar esta verificação depois
		/*
		byte[] b1 = ajustZerosLeft(new UtilMathBase(Base.HEX).otherToDecimalInBytes(hex), lenHex/2);
		if( !Arrays.equals(resp,b1) ){
			throw new APIBaseRuntimeException("Decode hex of UtilMathBase do not match!");
		}
		if( !Arrays.equals(resp,decodeHexCommonsCodec(hex)) ){
			throw new APIBaseRuntimeException("Decode hex of Commons Codec do not match!");
		}*/
		// não conferir no retorno para não gerar loop infinito!
		//String testeEnc = encodeHex(resp);
		//if(!hex.equalsIgnoreCase(testeEnc)){
		//	throw new APIBaseRuntimeException("Encode Hex do not match!");
		//}
		//************************* 
		
		return resp;
	}

	
	public static byte[] removeZerosLeft(byte[] src){
		int lastPosZero = -1;
		for(int i=0; i<src.length; i++){
			if(src[i]==0) lastPosZero = i;
			else break;
		}
		if(lastPosZero==src.length-1){
			return new byte[]{0};// se for tudo zero retorna somente um byte com zero
		}else if(lastPosZero>-1){
			return Arrays.copyOfRange(src, lastPosZero+1, src.length);
		}else{
			return src;
		}
	}
	
	public static byte[] completeZerosLeft(byte[] src, int totalSize){
		if(src.length<totalSize){
			byte[] resp = new byte[totalSize];
			System.arraycopy(src, 0, resp, totalSize-src.length, src.length);
			return resp;
		}else{
			return src;
		}
	}
	
	public static byte[] ajustZerosLeft(byte[] src, int totalSize){
		if(src.length<totalSize){
			return completeZerosLeft(src, totalSize);
		}else if(src.length>totalSize){
			return completeZerosLeft(removeZerosLeft(src),totalSize);
		}else{
			return src;
		}
	}
	
	
	public static String encodeBase64(byte[] bytes){
		return new BASE64Encoder().encode(bytes).replaceAll("\n", "").replaceAll("\r", "");
	}
	public static byte[] decodeBase64(String strBase64) throws IOException{
		return new BASE64Decoder().decodeBuffer(strBase64);
	}
	
	
	
	
	
	// ************** PARA CONFERIR COM O COMMONS CODEC **************
    private static String encodeHexCommonsCodec(byte[] data) {
        char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
        int l = data.length;
       char[] out = new char[l << 1];
       // two characters form the hex value.
       for (int i = 0, j = 0; i < l; i++) {
           out[j++] = DIGITS[(0xF0 & data[i]) >>> 4 ];
           out[j++] = DIGITS[ 0x0F & data[i] ];
       }
       return String.valueOf(out);
    }
    private static byte[] decodeHexCommonsCodec(String hex) {
    	char[] data = hex.toCharArray();
        int len = data.length;
        byte[] out = new byte[len >> 1];
        // two characters form the hex value.
        for (int i = 0, j = 0; j < len; i++) {
            int f = toDigitCommonsCodec(data[j], j) << 4;
            j++;
            f = f | toDigitCommonsCodec(data[j], j);
            j++;
            out[i] = (byte) (f & 0xFF);
        }

        return out;
    }
    private static int toDigitCommonsCodec(char ch, int index) throws APIBaseRuntimeException {
        int digit = Character.digit(ch, 16);
        if (digit == -1) throw new APIBaseRuntimeException("Illegal hexadecimal charcter " + ch + " at index " + index);
        return digit;
    }
    // ******************************************************
}
